/* 引入依赖API */
import { reactive, watch, nextTick, computed } from "vue";
import { INode, IVElement, IBinarySearchTree } from "@/interface";
import { checkExpand } from "@/interface/IUse";
import { initConfig, baseConfig } from "@/config";

type ItemType = "right" | "left";

/* 定义一些变量(全局变量) */

const useBSTItem = <T>(props: any, _this: any, args?: T & any) => {
  const { $tree, $message } = args;
  /* 定义一些变量(局部变量) */
  const itemProperty = reactive({
    nodeLeft: null,     // 左子节点
    nodeRight: null,    // 右子节点
    initLeft: false,    // 是否初始化左子节点
    initRight: false    // 是否初始化右子节点
  });

  /* 监听属性 */
  watch(() => itemProperty.nodeLeft, (nodeLeft) => {
    if (!itemProperty.initLeft && nodeLeft) {
      init("left", nodeLeft);
      itemProperty.initLeft = true;
    }
  });

  watch(() => itemProperty.nodeRight, (nodeRight) => {
    if (!itemProperty.initRight && nodeRight) {
      init("right", nodeRight)
      itemProperty.initRight = true;
    }
  });

  // 监听节点信息
  const watchNode = (node: any) => {
    /**
     * leftLine:    左边指针长度
     * rightLine:   右边指针长度
     * leftAngle:   左边指针角度
     * rightAngle:  右边指针角度
     * offset:      当前节点左偏移量(style.left)
     * top:         当前节点上偏移量(style.top)
    */
    /* 监听左指针长度 */
    watch(() => node.leftLine, (v) => setLineLength(node, node.$el, 'left', v));
    /* 监听右指针长度 */
    watch(() => node.rightLine, (v) => setLineLength(node, node.$el, 'right', v));
    /* 监听左指针角度 */
    watch(() => node.leftAngle, (v) => setLineAngle(node, v, 'left'));
    /* 监听右指针角度 */
    watch(() => node.rightAngle, (v) => setLineAngle(node, v, 'right'));
    /* 监听左偏移量 */
    watch(() => node.offset, (v) => {
      const carsh = checkCarsh(node);
      (carsh.length > 0) && expandCarsh(node, carsh);
      setOffsetLeft(node.$el, v);
    });
    /* 监听右偏移量 */
    watch(() => node.top, (v) => setOffsetLeft(node.$el, v));
  };

  /* 计算属性 */
  const hasNodePass = computed(() => props.node ? Number(props.node.key) === Number(props.passNode) : false);

  /* 定义方法 */
  // 接收子节点, 并调整其位置
  const init = (type: ItemType, component: any) => {
    if (!component) return false;
    nextTick(() => setPosition(component.$el, props.node[type], type));
  };

  // 初始化根节点
  const initRoot = (node: INode) => watchNode(node);

  // 节点被点击
  const nodeClick = (key: number) => $message({ type: 'info', content: `节点: ${key}` });

  // 设置当前子节点的位置
  const setPosition = (el: IVElement, node: INode, type: ItemType) => {
    // 设置节点属性
    setNodeProperty(el, node);

    // 监控当前节点
    watchNode(node);

    // 扩容
    expand(node, type);
  };

  // 统一设置指针线段的角度和长度
  const setLineAngleAndHeightTheUnify = (node: INode, className: string, angle: number) => {
    const el = node.$el;
    // @ts-ignore
    const relationLeft = node.relationLeft;
    const elm = el.querySelector(className);

    elm.style.transform = `rotateZ(${angle}deg)`;

    let child = node.right;
    let nodeOffset = node.offset;
    let childOffset = 0;
    // 如果新插入的节点位于扩容节点的左子树
    if (relationLeft) {
      child = node.left;
      nodeOffset -= initConfig.nodeWidth;
      childOffset = child.offset;
    } else {
      childOffset = child.offset - initConfig.nodeWidth;
    }

    const heightLine = initConfig.lineWidth;
    const condition = nodeOffset > childOffset;
    const verticalLine = condition ? nodeOffset - childOffset : childOffset - nodeOffset;
    const height = (heightLine + verticalLine);
    setLineLength(node, null, relationLeft ? 'left' : 'right', height, elm);
  }

  // 设置节点左偏移量
  const setOffsetLeft = (el: IVElement, offset: number) => (el.style.left = `${offset}px`);
  // 设置节点上偏移量
  const setOffsetTop = (el: IVElement, top: number) => (el.style.top = `${top}px`);
  // 设置指针角度
  const setLineAngle = (node: INode, angle: number, type: ItemType) => setLineAngleAndHeightTheUnify(node, `.binary-search-tree-line-${type}`, angle);
  // 设置指针长度
  const setLineLength = (node: INode, parent: HTMLElement | null, type: ItemType | null, value: number, elmNode?: HTMLElement) => {
    if (value > 90) value = 90;
    if (elmNode) {
      elmNode.style.height = `${value}px`;
    } else {
      const elm = parent?.querySelector(`.binary-search-tree-line-${type}`);
      (elm as HTMLElement).style.height = `${value}px`;
    }
    type === 'left' ? (node.leftLine = value) : (node.rightLine = value);

  }

  // 设置节点属性
  const setNodeProperty = (el: IVElement, node: INode) => {
    setOffsetLeft(el, node.offset);
    setOffsetTop(el, node.top);
    node.$el = el;
  };

  // 寻找产生碰撞的节点
  const produceCarsh: (n: INode, s: Array<INode>) => Array<INode> = (node, siblingList) => {
    return siblingList.filter(sibling => {
      const nodeOffset = Math.abs(node.offset);
      const siblingOffset = Math.abs(sibling.offset);
      const nodeGap = initConfig.baseNodeGap;
      // 如果sibling的key比当前节点的key大, 那就是sibling - node, 否则node - sibling
      const condition = sibling.key > node.key;
      const gap = condition ? siblingOffset - nodeOffset : nodeOffset - siblingOffset;
      return gap < nodeGap;
    });
  };

  // 碰撞检测
  const checkCarsh: (n: INode) => Array<INode> = (node) => {
    const siblingList = $tree.getSiblingNodes(node);
    if (siblingList.length > 0) {
      // 2.1.拿到碰撞节点
      const carsh = produceCarsh(node, siblingList);
      // 2.2.产生了碰撞
      return carsh;
    }
    return [];
  }

  // 检查是否需要扩容
  const checkExpand: checkExpand = (node) => {
    /**
     * 扩容的两条规则
     * 1.当前子节点与父节点的type相反(父节点不为root), 只扩容(节点宽度/2)+(gap/2)的距离 
     *  -> 同级子节点的相同父节点的父节点(前提是如果父节点不为root, 如果父节点为root则直接扩容root)
     * 2.两个节点发生碰撞, 扩容 节点宽度 + gap 的距离 
     *  -> 扩容当前子节点与碰撞子节点的共同父节点的父节点(前提是如果父节点不为root, 如果父节点为root则直接扩容root)
    */
    const result: any[] = [];

    // 1.获取节点
    // 1.1.拿到父节点
    const parent = node.parent;

    // 2.先检测是否发生碰撞(第二个扩容规则)
    const carsh = checkCarsh(node);
    // 2.2.产生了碰撞
    if (carsh.length > 0) result.push({ expand: true, carsh, isCarsh: true });

    // 3.判断第一个扩容规则, 找到同级子节点, 直接作为碰撞节点返回
    if (!parent.isRoot && node.isLeft !== parent.isLeft) {
      // 3.1.拿到兄弟节点
      const siblingList = $tree.getSiblingNodes(node);
      // 3.2.如果返回的carsh为空数组, 则直接扩容根节点, 这里好像应该扩容 父级的父级节点(爷爷节点)
      const carsh = produceCarsh(node, siblingList);
      result.push({ expand: true, carsh, isCarsh: false });
    }
    return result;
  };

  // 扩容
  const expand = (node: INode, type: ItemType) => {
    const result = checkExpand(node);
    if (result.length > 0) {
      result.forEach(item => {
        const { expand, carsh, isCarsh } = item;
        // 代表需要扩容
        if (expand) {
          // 直接扩容根节点, 好像是扩容父级的父级节点
          if (carsh.length < 1 && !isCarsh) {
            // expandRoot(node);
            // 在扩容根节点的基础上, 再扩容一下其他节点
            const grandpa = $tree.searchGrandpaNode(node);
            expandAssignNode(grandpa, node);
          }
          // 与碰撞扩容规则一样, 只是扩容大小不同(伪碰撞扩容)
          if (carsh.length > 0 && !isCarsh) {
            if (carsh.length > 1) throw new Error("碰撞节点不该为多个");
            expandCarshFake(node, carsh);
          }
          // 碰撞扩容
          if (isCarsh) {
            expandCarsh(node, carsh);
          }
        }
      })
    }
  };

  // 扩容子节点
  const cycle = (node: INode, isLeft: boolean, expandNodeGap: number) => {
    if (node) {
      const offset = node.offset;
      // 如果是左子树, 就像左移(减少), 如果是右子树 就向右移(增加)
      node.offset = isLeft ? (offset - expandNodeGap) : (offset + expandNodeGap);
      cycle(node.left, isLeft, expandNodeGap);
      cycle(node.right, isLeft, expandNodeGap);
    }
  };

  // 改变指针角度
  const resetPointLineAngle: (n: INode, l: boolean) => boolean = (node, isLeft) => {
    const angleKey = isLeft ? "leftAngle" : "rightAngle";
    const expandAngle = initConfig.lineExpandAngle;
    const newAngle = isLeft ? (node[angleKey] + expandAngle) : (node[angleKey] - expandAngle);
    // 如果新的角度小于等于最大角度才会赋值
    if (Math.abs(newAngle) <= initConfig.maxAngle) {
      node[angleKey] = newAngle;
      return true;
    }
    return false;
  };

  // 获取子树以及扩容间隔
  const getChildrenAndExpandGap: (node: INode, key: ItemType) => { children: INode, expandNodeGap: number } = (node, key) => {
    const children = node[key];
    const expandNodeGap = baseConfig.levelInterval / 2 + 10;
    return { children, expandNodeGap };
  }

  // 统一扩容
  const expandCommon = (node: INode, isLeft: boolean, childKey: ItemType) => {
    // 2.1.改变指针角度
    const success = resetPointLineAngle(node, isLeft);
    if (success) {
      // 3.对 左子树/右子树 进行统一 左/右 移动
      // 3.1.拿到子树和扩容间隔
      const { children, expandNodeGap } = getChildrenAndExpandGap(node, childKey);
      // 3.2.对子树所有节点进行扩容操作
      cycle(children, isLeft, expandNodeGap);
    } else {
      // 如果要扩容的节点角度达到了临界值, 则取对应子树的第一个子节点作为扩容节点
      // @ts-ignore
      node[childKey].relationLeft = node.relationLeft;
      expandCommon(node[childKey], isLeft, childKey);
    }
  }
  // 通用扩容抽象
  const expandCommonAbstract = (node: INode, expand: INode = $tree.getRoot()) => {
    // 找到node属于expandNode的左子树还是右子树
    const { direction, isLeft } = expandDirection(node, expand);
    expand.relationLeft = isLeft;
    expandCommon(expand, isLeft, direction);
  }
  // 获取扩容方向
  const expandDirection: (n: INode, p?: INode) => { direction: ItemType, isLeft: boolean } = (node, parent) => {
    const direction: ItemType = $tree.getChildrenFlag(node, parent);
    const isLeft = direction === "left";
    return { direction, isLeft };
  }

  // 指定扩容某个节点
  const expandAssignNode = (expandNode: INode, node: INode) => expandCommonAbstract(node, expandNode);

  // 伪碰撞扩容
  const expandCarshFake = (node: INode, carsh: Array<INode>) => {
    // 1.找到共同父节点
    const equalParents: Array<INode> = carsh.map((item: INode) => $tree.getParentEqual(node, item));
    if (equalParents.length > 1) throw new Error("共同父节点不该为多个");
    if (equalParents.length < 1) throw new Error("共同节点少于一个");
    const equalParent: INode = equalParents[0];
    // 2.确认要扩容的子树方向
    const { direction, isLeft } = expandDirection(node, equalParent);
    // 3.改变指针角度
    // @ts-ignore
    equalParent.relationLeft = isLeft;
    expandCommon(equalParent, isLeft, direction);
  };

  // 碰撞扩容, 扩容之前再检测一遍 是否真实碰撞
  const expandCarsh = (node: INode, carsh: Array<INode>) => {
    nextTick(() => {
      const _carsh = checkCarsh(node);
      if (_carsh.length > 0) {
        // 进行扩容操作
        // 1.找到共同父节点
        expandCarshFake(node, _carsh);
      }
    });
  };

  /* 接收子节点 */
  const receiveNodeLeft = (component: any) => (itemProperty.nodeLeft = component);
  const receiveNodeRight = (component: any) => (itemProperty.nodeRight = component);

  /* 统一暴露 */
  return {
    initRoot,
    itemProperty,
    hasNodePass,
    receiveNodeLeft,
    receiveNodeRight,
    nodeClick
  };
};

export default useBSTItem;
export { useBSTItem };
